#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <netdb.h>

#include "client.h"

static unsigned char hexchars[] = "0123456789ABCDEF";
int url_encode(char const *s, char *dest, int len)
{
    register unsigned char c;
    unsigned char *to;
    unsigned char const *from, *end, *bound;

    from = (unsigned char *)s;
    to = (unsigned char *) dest;

    end = (unsigned char *)s + strlen(s);
    bound = to + len - 4;

    while (from < end && to < bound) {
        c = *from++;

        if (c == ' ') {
            *to++ = '+';
        } else if ((c < '0' && c != '-' && c != '.') ||
                   (c < 'A' && c > '9') ||
                   (c > 'Z' && c < 'a' && c != '_') ||
                   (c > 'z')) {
            to[0] = '%';
            to[1] = hexchars[c >> 4];
            to[2] = hexchars[c & 15];
            to += 3;

        } else {
            *to++ = c;
        }
    }

    *to = 0;
    return (to - (unsigned char *) dest);
}

http_entity_t *client_request(const char *method, 
    const char *host,
    const char *port,
    const char *uri,
    const char *auth,
    const char *data)
{
    /* entity */
    static http_entity_t entity;
    http_entity_t *http = &entity;

    /* 连接 */
    struct addrinfo hints;
    struct addrinfo *result, *rp;
    int sockfd, s;

    memset(&hints, 0, sizeof(struct addrinfo));
    hints.ai_family = AF_INET;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_flags = 0;
    hints.ai_protocol = 0;

    s = getaddrinfo(host, port, &hints, &result);
    if (s != 0) {
       return NULL;
    }

    for (rp = result; rp != NULL; rp = rp->ai_next) {
        sockfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
        if (sockfd == -1) {
            continue;
        }
        if (connect(sockfd, rp->ai_addr, rp->ai_addrlen) != -1) {
            break;
        }
        close(sockfd);
    }

    if (rp == NULL) {
        return NULL;
    }

    freeaddrinfo(result);

    /* 请求 */
    http_init(http);

    strcpy(http->method, method);

    if (uri[0] != '/') {
        strcpy(http->uri, "/");
        strcat(http->uri, uri);
    } else {
        strcpy(http->uri, uri);
    }

    http_add_header(http, "Host", host);
    if (auth != NULL) {
        http_add_header(http, "Auth", auth);
    }

    if (strcmp(method, "POST") == 0) {
        http_add_header(http, "Content-Type", "application/x-www-form-urlencoded");
    }
 
    if (data != NULL && strlen(data) > 0) {
        http_append_to_buffer(http, data);
    }

    http_request(http, sockfd);

    /* 解析 */
    http_init(http);
    http_read(http, sockfd);
    http_parse(http);
    http_discard(http, sockfd);
    http_close(http, sockfd);

    /* 返回 */
    return http;
}

http_entity_t *client_request_ex(const char *method, 
    const char *url,
    const char *auth,
    const char *data)
{
    /* 静态buffer，用于存储解析结果 */
    static char buffer[1024];

    /* 解析 host, port, path */
    const char *p, *start;
    char *b, *host, *port, *path;

    /* p -> url, b -> buffer */
    int n, len = strlen(url);
    if (len <= 0 || len >= sizeof(buffer))
        return NULL;

    if ((p = strstr(url, "://")) == NULL) {
        p = url;
    } else {
        p += 3;
    }
    start = p;

    /* host */
    for (n = 0; !(*p == ':' || *p == '/' || *p == '\0'); n++, p++);

    b = buffer;
    host = b;
    strncpy(b, start, n);
    b += n;
    b[0] = 0;
    b++;

    /* port */
    start = p;
    n = 0;
    if (*p == ':') {
        p++;
        start++;
        for (; !(*p == '/' || *p == '\0'); n++, p++);
    }

    port = b;

    if (n == 0) {
        strcpy(b, "80");
        b += 3;
    } else {
        strncpy(b, start, n);
        b += n;
        b[0] = 0;
        b++;
        start = p;
    }

    /* path */
    path = b;
    if (strlen(p) > 0) {
        strcpy(b, p);
    } else {
        strcpy(b, "/");
    }

    /* 调用 */
    return client_request(method, host, port, path, auth, data);
}

